#include "SaveManager.hpp"
#include <sys/time.h>

#define	MOVE_CMD				0x00
#define	PAWNPROMOTION_CMD 		0x01
#define	CASTLING_CMD 			0x02
#define NB_TURNS_CMD			0x10
#define BLACK_CMD 				0xF2
#define WHITE_CMD 				0xF1
#define PPROMOTE_QUEEN_CMD		0xE0
#define PPROMOTE_BISHOP_CMD 	0xE1
#define PPROMOTE_ROOK_CMD 		0xE2
#define PPROMOTE_KNIGHT_CMD 	0xE3
#define CASTLING_QUEEN_CMD 		0xD0
#define CASTLING_KING_CMD 		0xD1
#define END_CMD					0x64
#define ERROR_CMD				0xFF
#define MAX_X					0x07
#define MAX_Y					0x07
#define FILE_HEADER				"ChessGameSave(c)ONIO\n"
#define STRLEN(s)				((sizeof(s)/sizeof(s[0])) - 1)

#define LB_SHIFT()																						\
	if (load_buffer.size() == 0)																		\
		throw new CriticalException("SaveManager:: trying to shift load_buffer far beyond the sun");	\
	load_curbyte++;																						\
	load_buffer.pop_front();
	
#define LB_CURRENT()		(load_buffer.front())
#define LB_FINISHED()		(load_buffer.size() == 0)

#define LB_THROW_INVALID()																												\
		sprintf(SaveManager::curbyte_str, "%.8X", load_curbyte);																				\
		throw new WarnException(string(string("Invalid file format at byte ") + string(SaveManager::curbyte_str)).c_str());


const char SaveManager::header[] = FILE_HEADER;

char SaveManager::curbyte_str[80];

SaveManager* SaveManager::instance = 0;

SaveManager* SaveManager::getInstance()
{
	if (SaveManager::instance == 0)
		SaveManager::instance = new SaveManager();
		
	return (SaveManager::instance);
}

SaveManager::SaveManager()
{}


SaveManager::~SaveManager()
{}



void SaveManager::save(string filename, const map<unsigned int, list<Action*> > & p1_actions, const map<unsigned int, list<Action*> > & p2_actions)
{
	FILE* fp = 0;
	list<unsigned char>::iterator 	it;
	unsigned char					byte;
	
	try
	{
		


#ifdef TIME_MESURE
		struct timeval tv1;
		struct timeval tv2;
		
		gettimeofday(&tv1, NULL);
#endif

	
		if ((fp = fopen(filename.c_str(), "wb")) == 0)
			throw new WarnException(string(string("Unable to open file ") + filename).c_str());	
		
		fprintf(fp, header);
		
		writeActions(p1_actions, p2_actions);
		save_buffer.push_back(END_CMD);
		
		for (it = save_buffer.begin() ; it != save_buffer.end() ; it++)
		{
			byte = *it;
			fwrite(&byte, 1, 1, fp);
		}
		

#ifdef TIME_MESURE	
		gettimeofday(&tv2, NULL);
		printf("TIME %d\n", (tv2.tv_usec - tv1.tv_usec));
#endif			


		fclose(fp);
		
		save_buffer.clear();
	}
	catch(Exception* e)
	{	throw e;	}
}


void SaveManager::load(string filename, LoadActions* load_actions)
{
	if (load_actions == 0)
		throw new CriticalException("SaveManager::load : load_actions given is NULL !");
	
	try
	{
		FILE*	fp = 0;
		char file_header[44];
		unsigned char	byte;
		
		load_buffer.clear();
		load_curbyte = 0;
		
		if ((fp = fopen(filename.c_str(), "rb")) == 0)
			throw new WarnException(string(string("Unable to open file ") + filename).c_str());
			
		if (fgets(file_header, STRLEN(FILE_HEADER) + 1, fp) == 0)
			throw new WarnException(string(string("Unable to read header in file  ") + filename).c_str());
		
		if (strncmp(file_header, header, STRLEN(FILE_HEADER)) != 0)
			throw new WarnException(string(string("Bad file header  : ") + header).c_str());
			
		while (!feof(fp))
		{
			fread(&byte, 1, 1, fp);
			load_buffer.push_back(byte);
		}
		
		fclose(fp);
		
		parseLoadBuffer(load_actions);
		
	}
	catch(Exception* e)
	{
		throw e;
	}
}

void SaveManager::parseLoadBuffer(LoadActions* load_actions)
{
	try
	{
		Action* cur = 0;
		unsigned int nb_turn_cur = 1;
		unsigned int nb_turn_tmp;
		
		while (LB_CURRENT() != END_CMD)
		{
			switch (LB_CURRENT())
			{
				case NB_TURNS_CMD:
					LB_SHIFT();
					nb_turn_tmp = readTurnNumber();
					
					if (!((nb_turn_tmp == nb_turn_cur + 1) || (nb_turn_tmp == nb_turn_cur)))
					{	LB_THROW_INVALID();		}
					
					nb_turn_cur = nb_turn_tmp;
					break;
					
				case MOVE_CMD:
					LB_SHIFT();
					cur = readMoveAction();
					break;
					
				case PAWNPROMOTION_CMD:
					LB_SHIFT();
					cur = readPawnPromotionAction();
					break;
					
				case CASTLING_CMD:
					LB_SHIFT();
					cur = readCastlingAction();
					break;
					
				default:
					LB_THROW_INVALID();
			}
			
			
			if (cur != 0)
				((cur->getColor() == Chess::WHITE) ? load_actions->p1_actions : load_actions->p2_actions)[nb_turn_cur].push_back(cur);
			
			cur = 0;
		}
	}
	catch (Exception* e)
	{	throw e;	}
}

void SaveManager::writeActions(const map<unsigned int, list<Action*> >& p1_actions, const map<unsigned int, list<Action*> >& p2_actions)
{
	unsigned int	i = 0;
	list<Action*>::const_iterator it;
	
	try
	{		
		for (i = 1 ; i < p1_actions.size() + 1 ; i++)
		{	
			writeTurnNumber(i);
			
			if (p1_actions.find(i) == p1_actions.end())
				break;
			
			for (it = p1_actions.find(i)->second.begin() ; it != p1_actions.find(i)->second.end() ; it++)
				writeAction(*it);
			
			if (p2_actions.find(i) == p2_actions.end())
				break;
			
			for (it = p2_actions.find(i)->second.begin() ; it != p2_actions.find(i)->second.end()  ; it++)
				writeAction(*it);
				
			
		}
	}
	catch(Exception *e)
	{	throw e;	}
}

void SaveManager::writeAction(Action* action)
{
	try
	{
		switch(action->getType())
		{
			case Action::MOVE:
				MoveAction* maction;
				if ((maction = static_cast<MoveAction*>(action)) == 0)
					throw new CriticalException("SaveManager::writeAction : failed on dynamic cast Move Action !");
				writeMoveAction(maction);
				break;
				
			case Action::PAWN_PROMOTION:
				PawnPromotionAction* ppaction;
				if ((ppaction = static_cast<PawnPromotionAction*>(action)) == 0)
					throw new CriticalException("SaveManager::writeAction : failed on dynamic cast PawnPromotion Action !");
				writePawnPromotionAction(ppaction);
				break;
				
			case Action::CASTLING:
				CastlingAction* caction;
				if ((caction = static_cast<CastlingAction*>(action)) == 0)
					throw new CriticalException("SaveManager::writeAction : failed on dynamic cast Castling Action !");
				writeCastlingAction(caction);
				break;
		}
	}
	catch(Exception* e)
	{	throw e;	}
}

// read NBTURNS
unsigned int SaveManager::readTurnNumber()
{
	unsigned char	nb_t00;
	unsigned char	nb_t01;
	unsigned char	nb_t02;
	unsigned char	nb_t03;
	unsigned int	nb_turn = 0;
	
	
	nb_t03 = LB_CURRENT();
	LB_SHIFT();
	nb_t02 = LB_CURRENT();
	LB_SHIFT();
	nb_t01 = LB_CURRENT();
	LB_SHIFT();
	nb_t00 = LB_CURRENT();
	LB_SHIFT();
	
	nb_turn |= nb_t03;
	nb_turn <<= 8; 
	nb_turn |= nb_t02;
	nb_turn <<= 8;
	nb_turn |= nb_t01;
	nb_turn <<= 8;
	nb_turn |= nb_t00;
	
	return (nb_turn);
}

// write TURNCMD NBTURNS
void SaveManager::writeTurnNumber(unsigned int nb_turns)
{
	unsigned char	nb_t00;
	unsigned char	nb_t01;
	unsigned char	nb_t02;
	unsigned char	nb_t03;

	nb_t00 = (unsigned char)(nb_turns & 0x000000FF);
	nb_t01 = (unsigned char)((nb_turns & 0x0000FF00) >> 0x08);
	nb_t02 = (unsigned char)((nb_turns & 0x00FF0000) >> 0x10);
	nb_t03 = (unsigned char)((nb_turns & 0xFF000000) >> 0x18);
	
	save_buffer.push_back(NB_TURNS_CMD);
	
	save_buffer.push_back(nb_t03);
	save_buffer.push_back(nb_t02);
	save_buffer.push_back(nb_t01);
	save_buffer.push_back(nb_t00);
}

// read COLOR
Chess::Color SaveManager::readColor()
{	
	switch(LB_CURRENT())
	{
		case WHITE_CMD:
			LB_SHIFT();
			return Chess::WHITE;
			
		case BLACK_CMD:
			LB_SHIFT();
			return Chess::BLACK;
			
		default:
			LB_THROW_INVALID();
	}
}


//	write COLOR
void SaveManager::writeColor(Chess::Color color)
{
	unsigned char color_cmd = (color == Chess::WHITE) ? WHITE_CMD : BLACK_CMD;
	
	save_buffer.push_back(color_cmd);
}

// read COLOR x y
Position SaveManager::readPosition()
{
	try
	{
		unsigned int x;
		unsigned int y;
		
		Chess::Color color = readColor();
		
		if (LB_CURRENT() > MAX_X)
		{	LB_THROW_INVALID();	}
		
		x = LB_CURRENT();
		LB_SHIFT();
		
		if (LB_CURRENT() > MAX_Y)
		{	LB_THROW_INVALID();	}
		
		y = LB_CURRENT();
		LB_SHIFT();
		
		return (Position(x, y, color));
	}
	catch(Exception* e)
	{	throw e;	}
}


// COLOR	X	Y
void SaveManager::writePosition(const Position& pos)
{
	
	unsigned int x = pos.getX();
	unsigned int y = pos.getY();
	
	unsigned char x_cmd = (unsigned char)(x & 0x000000FF);
	unsigned char y_cmd = (unsigned char)(y & 0x000000FF);
	
	writeColor(pos.getColor());
	save_buffer.push_back(x_cmd);
	save_buffer.push_back(y_cmd);
}

// read COLOR POS1 POS2
MoveAction* SaveManager::readMoveAction()
{
	try
	{
		Chess::Color color = readColor();
		Position pos1 = readPosition();
		Position pos2 = readPosition();
		
		
		return (new MoveAction(color, pos1, pos2));
	}
	catch(Exception* e)
	{	throw e;	}
}


// write MOVE_CMD COLOR		POS1	POS2

void SaveManager::writeMoveAction(MoveAction* maction)
{
	Position pos1 = maction->getPos1();
	Position pos2 = maction->getPos2();
	
	save_buffer.push_back(MOVE_CMD);
	writeColor(maction->getColor());
	writePosition(pos1);
	writePosition(pos2);
}


// read PPROMOTETYPE
Piece::Type		SaveManager::readPromotionType()
{	
	switch(LB_CURRENT())
	{
		case PPROMOTE_QUEEN_CMD:
			LB_SHIFT();
			return (Piece::QUEEN);
		
		case PPROMOTE_KNIGHT_CMD:
			LB_SHIFT();
			return (Piece::KNIGHT);
			
		case PPROMOTE_BISHOP_CMD:
			LB_SHIFT();
			return (Piece::BISHOP);
			
		case PPROMOTE_ROOK_CMD:
			LB_SHIFT();
			return (Piece::ROOK);	
		
		default:
			LB_THROW_INVALID();
	}	
}

// write PPROMOTETYPE
void SaveManager::writePromotionType(Piece::Type type)
{
	
	unsigned char type_cmd = 0;
	
	switch(type)
	{
		case Piece::KING:
			throw new CriticalException("SaveManager::writePromotionType : trying to write a promotion to king !");
			break;
			
		case Piece::PAWN:
			throw new CriticalException("SaveManager::writePromotionType : trying to write a promotion to pawn !");
			break;
		
		case Piece::BISHOP:
			type_cmd = PPROMOTE_BISHOP_CMD;
			break;
			
		case Piece::KNIGHT:
			type_cmd = PPROMOTE_KNIGHT_CMD;
			break;
			
		case Piece::QUEEN:
			type_cmd = PPROMOTE_QUEEN_CMD;
			break;
			
		case Piece::ROOK:
			type_cmd = PPROMOTE_ROOK_CMD;
			break;
	}
	
	save_buffer.push_back(type_cmd);
}

// read COLOR POS PPROMOTETYPE
PawnPromotionAction*	SaveManager::readPawnPromotionAction()
{
	try
	{
		Chess::Color color = readColor();
		Position pos = readPosition();
		Piece::Type ptype = readPromotionType();
		
		return (new PawnPromotionAction(color, pos, ptype));
	}
	catch(Exception* e)
	{
		throw e;
	}
}


//	write PPROMOTECMD	 COLOR	POS	PPROMOTETYPE
void SaveManager::writePawnPromotionAction(PawnPromotionAction* ppaction)
{
	try
	{
		Position pos = ppaction->getPosition();
		
		save_buffer.push_back(PAWNPROMOTION_CMD);
		writeColor(ppaction->getColor());
		writePosition(ppaction->getPosition());
		writePromotionType(ppaction->getPieceType());
	}
	catch	(Exception* e)
	{	throw e;	}
}

// read CASTLINGTYPE
CastlingAction::Type	SaveManager::readCastlingType()
{
	switch(LB_CURRENT())
	{
		case CASTLING_QUEEN_CMD:
			LB_SHIFT();
			return (CastlingAction::QUEEN_SIDE);
			
		case CASTLING_KING_CMD:
			LB_SHIFT();
			return (CastlingAction::KING_SIDE);
			
		default:
			LB_THROW_INVALID();
	}
}

// read CASTLINGTYPE
void SaveManager::writeCastlingType(CastlingAction::Type type)
{
	unsigned char type_cmd = 0;
	
	switch (type)
	{
		case CastlingAction::QUEEN_SIDE:
			type_cmd = CASTLING_QUEEN_CMD;
			break;
			
		case CastlingAction::KING_SIDE:
			type_cmd = CASTLING_KING_CMD;
			break;
	}
	
	save_buffer.push_back(type_cmd);
}

// read COLOR CASTLINGTYPE
CastlingAction*		SaveManager::readCastlingAction()
{
	try
	{
		Chess::Color color 	= readColor();
		CastlingAction::Type type = readCastlingType();
		
		return (new CastlingAction(color, type));
	}
	catch(Exception* e)
	{	throw e;	}
}

// write CASTLINGCMD  COLOR 	CASTLINGTYPE
void SaveManager::writeCastlingAction(CastlingAction* caction)
{
	try
	{
		save_buffer.push_back(CASTLING_CMD);
		writeColor(caction->getColor());
		writeCastlingType(caction->getCastlingType());
	}
	catch	(Exception* e)
	{	throw e;	}	
}

